/* -*- c++ -*-
   external.cc - LLVMIR external interface code.
   Copyright (C) 2011 Matthew Wahab <mwb.cde@gmail.com>

   This file is part of GCC-LLVMIR.

   GCC-LLVMIR is free software; you can redistribute it and/or modify it under
   the terms of the GNU General Public License as published by the Free
   Software Foundation; either version 3, or (at your option) any later
   version.

   GCC-LLVMIR is distributed in the hope that it will be useful, but WITHOUT ANY
   WARRANTY; without even the implied warranty of MERCHANTABILITY or
   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
   for more details.

   You should have received a copy of the GNU General Public License
   along with GCC-LLVMIR; see the file COPYING3.  If not see
   <http://www.gnu.org/licenses/>.  */


/*
  external.c - Code that needs to be built as seperate from GCC code,
  to avoid interactions between the GCC headers and external headers.
*/

#include "llvm/LLVMContext.h"
#include "llvm/Module.h"
#include "llvm/Support/IRReader.h"

#include "clang/FrontendTool/Utils.h"
#include "clang/Frontend/CompilerInvocation.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/TextDiagnosticBuffer.h"
#include "clang/Frontend/FrontendOptions.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/CodeGen/CodeGenAction.h"
#include "clang/Frontend/FrontendDiagnostic.h"
#include "clang/Basic/SourceManager.h"

namespace llvmir
{

  /* Open and parse an LLVM assembler or bit-code file, returning the
     module or 0 on failure. */
  llvm::Module*
  parse_asm_file(llvm::LLVMContext* llvm_context,
                 llvm::SMDiagnostic* llvm_diagnostic,
                 const std::string& filename)
  {
    llvm::Module* llvm_module = 0;

    // Read and parser the temporary file to get the LLVM module.
    llvm_module = llvm::ParseIRFile(filename,
                                    *llvm_diagnostic,
                                    *llvm_context);

    return llvm_module;
  }
  
#ifdef LLVMIR_USE_CLANG
  /* Allocate an array of strings, with the contents of the
     vector. The number of elements in the array is the same as the
     number of strings in the vector (in llvmir.cc).
   */
  typedef std::vector<std::string> StringSeq;
  const char** array_of_strseq(const StringSeq& string_seq);

  /* Open and parse a file with clang. */
  llvm::Module*

  clang_parse_file(llvm::LLVMContext* llvm_context,
                   llvm::SMDiagnostic* llvm_diagnostic,
                   const StringSeq& options_seq,
                   const std::string& filename,
                   const std::string& temp_filename)
  {
    llvm::Module* llvm_module = 0;
    llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> 
      llvm_diag_Id(new clang::DiagnosticIDs());
    clang::DiagnosticsEngine clang_diagnostic(llvm_diag_Id);
    bool clang_result = false;
    clang::CompilerInstance clang_object;
    clang::CompilerInstance *clang = &clang_object;
    StringSeq cmdline;
    unsigned arg_count = 0;
    const char** arg_array = 0;

    // Set-up a fake command line to pass to the Clang invocation.
    // Specify the executable
    cmdline.push_back("gcc-llvmir");

    // Specify the default input language, before the user specified
    // options so that it can be over-ridden.

    cmdline.push_back("-x");
    cmdline.push_back("c");

    // Add the options.
    for (StringSeq::const_iterator
           itr = options_seq.begin(),
           end = options_seq.end();
         itr != end;
         ++itr)
      {
        const std::string itr_str = *itr;
        cmdline.push_back(itr_str);
      }

    // Specify the output to put in the temporary file.
    cmdline.push_back("-emit-llvm");

    // Add the output file.
    cmdline.push_back("-o");
    cmdline.push_back(temp_filename.c_str());

    // Add the input file.
    cmdline.push_back(filename.c_str());

    // Set-up the array that will be passed Clang.
    arg_array = array_of_strseq(cmdline);
    arg_count = cmdline.size();

    // Run clang, generating LLVM bitcode in the temporary file.
    clang::CompilerInvocation::CreateFromArgs(clang->getInvocation(),
                                              &arg_array[1],
                                              &arg_array[arg_count], 
                                              clang_diagnostic);

    // Set-up the diagnostics.
    clang->createDiagnostics(arg_count, arg_array);

    // Run clang
    clang_result = clang::ExecuteCompilerInvocation(clang);
    if (!clang_result)
      return 0;

    // Read and parse the temporary file.
    llvm_module = llvm::ParseIRFile(temp_filename,
                                    *llvm_diagnostic,
                                    *llvm_context);

    // Clean up.
    delete [] arg_array;
    arg_count = 0;

    return llvm_module;
  }
#endif // LLVMIR_USE_CLANG

} // namespace llvmir
